/*
 * @(#)PatientQueryService.java
 * Date 2013-08-24
 * Version 1.0
 * Author Jim Horner
 * Copyright (c)2012
 */


package us.hornerscorners.lamppost.pix.service.es;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;

import javax.enterprise.context.ApplicationScoped;

import javax.enterprise.inject.Alternative;

import javax.inject.Inject;

import javax.xml.bind.JAXBElement;

import org.apache.commons.lang3.StringUtils;

import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

import org.hl7.v3.II;
import org.hl7.v3.PRPAIN201309UV02;
import org.hl7.v3.PRPAIN201309UV02QUQIMT021001UV01ControlActProcess;
import org.hl7.v3.PRPAIN201310UV02;
import org.hl7.v3.PRPAMT201307UV02ParameterList;
import org.hl7.v3.PRPAMT201307UV02PatientIdentifier;
import org.hl7.v3.PRPAMT201307UV02QueryByParameter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import us.hornerscorners.lamppost.config.BasicServiceConfig;

import us.hornerscorners.lamppost.exception.ConversionException;
import us.hornerscorners.lamppost.exception.ServiceActionError;
import us.hornerscorners.lamppost.exception.ServiceActionException;
import us.hornerscorners.lamppost.exception.SeverityType;

import us.hornerscorners.lamppost.hl7v3common.constant.AckResponse;

import us.hornerscorners.lamppost.ihecommon.config.IHEConfig;
import us.hornerscorners.lamppost.ihecommon.config.IHEServiceType;

import us.hornerscorners.lamppost.ihecommon.service.ElasticSearchSource;

import us.hornerscorners.lamppost.model.patient.Patient;

import us.hornerscorners.lamppost.pix.model.PatientQueryResponse;

import us.hornerscorners.lamppost.pix.service.IPatientQueryService;

import us.hornerscorners.lamppost.pix.transformer.hl7.PRPAIN201310UV02Transformer;

import static org.elasticsearch.index.query.QueryBuilders.termQuery;


/**
 *
 * @author jim
 */
@ApplicationScoped
@Alternative
public class PatientQueryService implements IPatientQueryService {

    /** Field description */
    private static final String ERROR_CODE = "REG";

    /** Field description */
    private static final Class ERROR_LOC = PatientQueryService.class;

    /** Field description */
    @Inject
    private IHEConfig config;

    /** Field description */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Field description */
    @Inject
    private PRPAIN201310UV02Transformer responseTransformer;

    /** Field description */
    private BasicServiceConfig serviceConfig;

    /** Field description */
    @Inject
    private ElasticSearchSource source;

    /**
     * Constructs ...
     *
     */
    PatientQueryService() {
        super();
    }

    /**
     * Method description
     *
     *
     * @param patientIdentifier
     *
     * @return
     */
    private QueryBuilder createQuery(II patientIdentifier) {

        BoolQueryBuilder result = QueryBuilders.boolQuery();

        String root = patientIdentifier.getRoot();
        String extension = patientIdentifier.getExtension();

        BoolQueryBuilder queryId = QueryBuilders.boolQuery();

        queryId.must(termQuery("identifiers.root", root));

        BoolQueryBuilder queryOther = QueryBuilders.boolQuery();

        queryOther.must(termQuery("otherIds.identifiers.root", root));

        if (StringUtils.isNotBlank(extension)) {

            queryId.must(termQuery("identifiers.extension", extension));
            queryOther.must(termQuery("otherIds.identifiers.extension", extension));
        }

        result.minimumNumberShouldMatch(1);
        result.should(queryId);
        result.should(queryOther);

        return result;
    }

    /**
     * Method description
     *
     *
     * @param patientIdentifier
     *
     * @return
     * @throws us.hornerscorners.lamppost.exception.ServiceActionException
     */
    protected List<Patient> executeQuery(II patientIdentifier)
            throws ServiceActionException {

        List<Patient> result = new ArrayList<>();

        QueryBuilder query = createQuery(patientIdentifier);

        result.addAll(this.source.runQuery(query, Patient.class));

        return result;
    }

    /**
     * Method description
     *
     *
     * @param queryParameters
     *
     * @return
     */
    private II parsePatientIdentifier(PRPAMT201307UV02QueryByParameter queryParameters)
            throws ConversionException {

        PRPAMT201307UV02ParameterList paramList = queryParameters.getParameterList();

        if (paramList == null) {

            String msg = "No query parameters were provided.";
            ServiceActionError error = new ServiceActionError(ERROR_CODE, msg,
                                           SeverityType.Error);

            throw new ConversionException(ERROR_LOC, error);
        }

        List<PRPAMT201307UV02PatientIdentifier> identifiers =
            paramList.getPatientIdentifier();

        if (identifiers.isEmpty()) {

            String msg = "No patient identifiers were provided in the query parameters.";
            ServiceActionError error = new ServiceActionError(ERROR_CODE, msg,
                                           SeverityType.Error);

            throw new ConversionException(ERROR_LOC, error);
        }

        PRPAMT201307UV02PatientIdentifier identifier = identifiers.get(0);

        List<II> values = identifier.getValue();

        if (values.isEmpty()) {

            String msg = "Patient identifier query parameter contains no identifiers.";
            ServiceActionError error = new ServiceActionError(ERROR_CODE, msg,
                                           SeverityType.Error);

            throw new ConversionException(ERROR_LOC, error);
        }

        return values.get(0);
    }

    /**
     * Method description
     *
     *
     * @param prpa309
     *
     * @return
     */
    private PRPAMT201307UV02QueryByParameter parseQueryParameters(
            PRPAIN201309UV02 prpa309)
            throws ConversionException {

        PRPAMT201307UV02QueryByParameter result = null;

        PRPAIN201309UV02QUQIMT021001UV01ControlActProcess controlAct =
            prpa309.getControlActProcess();

        if (controlAct != null) {

            JAXBElement<PRPAMT201307UV02QueryByParameter> jaxbParameters =
                controlAct.getQueryByParameter();

            if (jaxbParameters != null) {

                result = jaxbParameters.getValue();
            }
        }

        if (result == null) {

            String msg = "Query contains no parameters.";
            ServiceActionError error = new ServiceActionError(ERROR_CODE, msg,
                                           SeverityType.Error);

            throw new ConversionException(ERROR_LOC, error);
        }

        return result;
    }

    /**
     * Method description
     *
     */
    @PostConstruct
    public void postConstruct() {

        this.serviceConfig =
            (BasicServiceConfig) this.config.findServiceConfig(IHEServiceType.PIX);
    }

    /**
     * Method description
     *
     *
     * @param prpa309
     *
     * @return
     *
     */
    @Override
    public PRPAIN201310UV02 retrieveIdentifiers(PRPAIN201309UV02 prpa309) {

        PRPAIN201310UV02 result = null;
        PRPAMT201307UV02QueryByParameter queryParameters = null;
        List<Patient> patients = new ArrayList<>();
        II patientIdentifier = null;

        try {

            queryParameters = parseQueryParameters(prpa309);

            patientIdentifier = parsePatientIdentifier(queryParameters);

        } catch (ServiceActionException e) {

            // TODO capture exception
            // TODO need to set result
        }

        if (patientIdentifier != null) {

            try {

                patients.addAll(executeQuery(patientIdentifier));

                PatientQueryResponse response =
                    new PatientQueryResponse(this.serviceConfig, prpa309,
                                             queryParameters, patients,
                                             AckResponse.CommitAck);

                result = this.responseTransformer.transform(response);

            } catch (ServiceActionException e) {

                // TODO capture exception
                // TODO need to set result
            }
        }

        return result;
    }
}
